;;   This file is part of scheme-GNUnet, a partial Scheme port of GNUnet.
;;   Copyright (C) 2003--2014 GNUnet e.V.
;;   Copyright (C) 2020 Maxime Devos <maxime.devos@student.kuleuven.be>
;;
;;   GNUnet is free software: you can redistribute it and/or modify it
;;   under the terms of the GNU Affero General Public License as published
;;   by the Free Software Foundation, either version 3 of the License,
;;   or (at your option) any later version.
;;
;;   GNUnet is distributed in the hope that it will be useful, but
;;   WITHOUT ANY WARRANTY; without even the implied warranty of
;;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;;   Affero General Public License for more details.
;;
;;   You should have received a copy of the GNU Affero General Public License
;;   along with this program.  If not, see <http://www.gnu.org/licenses/>.
;;
;;   SPDX-License-Identifier: AGPL-3.0-or-later
;;
;;   As a special exception to the GNU Affero General Public License,
;;   the file may be relicensed under any license used for
;;   most source code of GNUnet 0.13.1, or later versions, as published by
;;   GNUnet e.V.

;; Upstream file header:
;; @file fs/fs_uri.c
;; @brief Parses and produces uri strings.
;; @author Igor Wronsky, Christian Grothoff
;;
;; GNUnet URIs are of the general form "gnunet://MODULE/IDENTIFIER".
;; The specific structure of "IDENTIFIER" depends on the module and
;; maybe differenciated into additional subcategories if applicable.
;; This module only deals with fs identifiers (MODULE = "fs").
;; <p>
;;
;; This module only parses URIs for the AFS module.  The FS URIs fall
;; into four categories, "chk", "sks", "ksk" and "loc".  The first three
;; categories were named in analogy (!) to Freenet, but they do NOT
;; work in exactly the same way.  They are very similar from the user's
;; point of view (unique file identifier, subspace, keyword), but the
;; implementation is rather different in pretty much every detail.
;; The concrete URI formats are:
;;
;; </p>
;;
;; <ul><li>
;;
;; First, there are URIs that identify a file.  They have the format
;; "gnunet://fs/chk/HEX1.HEX2.SIZE".  These URIs can be used to
;; download the file.  The description, filename, mime-type and other
;; meta-data is NOT part of the file-URI since a URI uniquely
;; identifies a resource (and the contents of the file would be the
;; same even if it had a different description).
;;
;; </li><li>
;;
;; The second category identifies entries in a namespace.  The format
;; is "gnunet://fs/sks/NAMESPACE/IDENTIFIER" where the namespace
;; should be given in HEX.  Applications may allow using a nickname
;; for the namespace if the nickname is not ambiguous.  The identifier
;; can be either an ASCII sequence or a HEX-encoding.  If the
;; identifier is in ASCII but the format is ambiguous and could denote
;; a HEX-string a "/" is appended to indicate ASCII encoding.
;;
;; </li> <li>
;;
;; The third category identifies ordinary searches.  The format is
;; "gnunet://fs/ksk/KEYWORD[+KEYWORD]*".  Using the "+" syntax
;; it is possible to encode searches with the boolean "AND" operator.
;; "+" is used since it indicates a commutative 'and' operation and
;; is unlikely to be used in a keyword by itself.
;;
;; </li><li>
;;
;; The last category identifies a datum on a specific machine.  The
;; format is "gnunet://fs/loc/HEX1.HEX2.SIZE.PEER.SIG.EXPTIME".  PEER is
;; the BinName of the public key of the peer storing the datum.  The
;; signature (SIG) certifies that this peer has this content.
;; HEX1, HEX2 and SIZE correspond to a 'chk' URI.
;;
;; </li></ul>
;;
;; The encoding for hexadecimal values is defined in the hashing.c
;; module in the gnunetutil library and discussed there.

(library (gnu gnunet fs-uri (1 1))
  (export chk? make-chk chk-key chk-query
          chk-uri? make-chk-uri chk-uri-file-length chk-uri-chk
	  chk-uri-parse)
  (import (rnrs base)
          (rnrs records syntactic)
	  (gnu gnunet hashcode)
	  (gnu gnunet hashcode-ascii)
	  ;; TODO portability
	  (only (guile) make-regexp regexp-exec)
	  (only (ice-9 regex) match:substring)
	  (only (srfi srfi-2) and-let*))

  ;; Size of the individual blocks used for file-sharing.
  ;; TODO: what is the proper place to define this constant
  #;(define DBLOCK_SIZE (* 32 1024))

  ;; Content hash key
  (define-record-type (<content-hash-key> %make-content-hash-key chk?)
    (fields ;; Hash of the original content, used for encryption.
            ;; Of type <hash-code>.
            (immutable key chk-key)
            ;; Hash of the encrypted content, used for querying.
            ;; Of type <hash-code>
            (immutable query chk-query)))

  (define (make-chk key query)
    "Construct a <content-hash-key>"
    (assert (hashcode? key))
    (assert (hashcode? query))
    (%make-content-hash-key key query))

  ;; Information needed to retrieve a file (content-hash-key
  ;; plus file size)
  (define-record-type (<chk-uri> %make-chk-uri chk-uri?)
    (fields ;; Total size of the file referred to in bytes.
            (immutable file-length chk-uri-file-length)
            ;; Query and key of the top GNUNET_EC_IBlock.
            ;; Of type <content-hash-key>.
            (immutable chk chk-uri-chk)))

  ;; TODO: is this limitation on file size
  ;; merely a limit of the implementation?
  (define file-length-limit (expt 2 64))

  (define (make-chk-uri file-length chk)
    "Make a chk-URI"
    (assert (and (exact? file-length)
		 (integer? file-length)))
    (assert (and (<= 0 file-length)
		 (< file-length file-length-limit)))
    (assert (chk? chk))
    (%make-chk-uri file-length chk))

  ;; TODO: location URIs, ksk URIs?
  ;; Why does GNUnet have location URIs?

  (define chk-uri-parse
    (let ((rx
	   (make-regexp
	    "^gnunet://fs/chk/([0-9A-Z]+).([0-9A-Z]+).(0|[1-9][0-9]+)$")))
      (lambda (str)
	"Parse a ‘Content Hash Key’ URI, return @lisp{#f} in case of a syntax
error."
	(and-let* ((match (regexp-exec rx str))
		   (key-match (match:substring match 1))
		   (query-match (match:substring match 2))
		   (size-match (match:substring match 3))
		   (key-hashcode (ascii->hashcode key-match))
		   (query-hashcode (ascii->hashcode query-match))
		   (size (string->number size-match 10))
		   (size-ok (< size file-length-limit)))
	  (%make-chk-uri size
			 (%make-content-hash-key key-hashcode
						 query-hashcode)))))))

